package com.carey.blog.util;

import java.util.logging.Level;
import java.util.logging.Logger;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.carey.blog.framework.latke.Keys;
import com.carey.blog.framework.latke.model.User;
import com.carey.blog.framework.latke.repository.Query;
import com.carey.blog.framework.latke.repository.RepositoryException;
import com.carey.blog.model.Article;
import com.carey.blog.repository.ArticleRepository;
import com.carey.blog.repository.UserRepository;
import com.carey.blog.repository.impl.ArticleGAERepository;
import com.carey.blog.repository.impl.UserGAERepository;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;

/**
 * User utilities.
 * 
 */
public final class Users {

	/**
	 * Logger.
	 */
	private static final Logger LOGGER = Logger
			.getLogger(Users.class.getName());
	/**
	 * User repository.
	 */
	private UserRepository userRepository = UserGAERepository.getInstance();
	/**
	 * User service.
	 */
	private UserService userService = UserServiceFactory.getUserService();
	/**
	 * Article repository.
	 */
	private ArticleRepository articleRepository = ArticleGAERepository
			.getInstance();

	/**
	 * Determines whether if exists multiple users in current Carey Blog.
	 * 
	 * @return {@code true} if exists, {@code false} otherwise
	 * @throws JSONException
	 *             json exception
	 * @throws RepositoryException
	 *             repository exception
	 */
	public boolean hasMultipleUsers() throws JSONException, RepositoryException {
		final JSONArray users = userRepository.get(new Query()).getJSONArray(
				Keys.RESULTS);

		return 1 != users.length();
	}

	/**
	 * Can the current user access an article specified by the given article id?
	 * 
	 * @param articleId
	 *            the given article id
	 * @return {@code true} if the current user can access the article,
	 *         {@code false} otherwise
	 * @throws Exception
	 *             exception
	 */
	public boolean canAccessArticle(final String articleId) throws Exception {
		if (isAdminLoggedIn()) {
			return true;
		}

		final JSONObject article = articleRepository.get(articleId);
		final String currentUserEmail = getCurrentUser().getString(
				User.USER_EMAIL);

		if (!article.getString(Article.ARTICLE_AUTHOR_EMAIL).equals(
				currentUserEmail)) {
			return false;
		}

		return true;
	}

	/**
	 * Determines whether the current logged in user is a collaborate
	 * administrator(added via GAE Admin Console Permissions).
	 * 
	 * @return {@code true} if it is, {@code false} otherwise
	 */
	public boolean isCollaborateAdmin() {
		final boolean isUserLoggedIn = userService.isUserLoggedIn();
		if (!isUserLoggedIn) {
			return false;
		}

		final boolean isUserAdmin = userService.isUserAdmin();
		if (!isUserAdmin) {
			return false;
		}

		final com.google.appengine.api.users.User currentUser = userService
				.getCurrentUser();
		return !isSoloUser(currentUser.getEmail());
	}

	/**
	 * Checks whether the current request is made by logged in user(including
	 * default user and administrator lists in <i>users</i>).
	 * 
	 * @return {@code true} if the current request is made by logged in user,
	 *         returns {@code false} otherwise
	 */
	public boolean isLoggedIn() {
		final com.google.appengine.api.users.User currentUser = userService
				.getCurrentUser();
		if (null == currentUser) {
			return false;
		}

		return isSoloUser(currentUser.getEmail()) || userService.isUserAdmin();
	}

	/**
	 * Checks whether the current request is made by logged in administrator.
	 * 
	 * @return {@code true} if the current request is made by logged in
	 *         administrator, returns {@code false} otherwise
	 */
	public boolean isAdminLoggedIn() {
		return userService.isUserLoggedIn() && userService.isUserAdmin();
	}

	/**
	 * Gets the current user.
	 * 
	 * @return the current user, {@code null} if not found
	 */
	public JSONObject getCurrentUser() {
		final com.google.appengine.api.users.User currentUser = userService
				.getCurrentUser();
		if (null == currentUser) {
			return null;
		}

		final String email = currentUser.getEmail();
		return userRepository.getByEmail(email);
	}

	/**
	 * Determines whether the specified email is a user's email of this Solo
	 * application.
	 * 
	 * @param email
	 *            the specified email
	 * @return {@code true} if it is, {@code false} otherwise
	 */
	public boolean isSoloUser(final String email) {
		try {
			final JSONObject result = userRepository.get(new Query());
			final JSONArray users = result.getJSONArray(Keys.RESULTS);

			return existEmail(email, users);
		} catch (final Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			return false;
		}
	}

	/**
	 * Determines whether the specified email exits in the specified users.
	 * 
	 * @param email
	 *            the specified email
	 * @param users
	 *            the specified user
	 * @return {@code true} if exists, {@code false} otherwise
	 * @throws JSONException
	 *             json exception
	 */
	private boolean existEmail(final String email, final JSONArray users)
			throws JSONException {
		for (int i = 0; i < users.length(); i++) {
			final JSONObject user = users.getJSONObject(i);
			if (user.getString(User.USER_EMAIL).equalsIgnoreCase(email)) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Gets the {@link Users} singleton.
	 * 
	 * @return the singleton
	 */
	public static Users getInstance() {
		return SingletonHolder.SINGLETON;
	}

	/**
	 * Private default constructor.
	 */
	private Users() {
	}

	/**
	 * Singleton holder.
	 * 
	 */
	private static final class SingletonHolder {

		/**
		 * Singleton.
		 */
		private static final Users SINGLETON = new Users();

		/**
		 * Private default constructor.
		 */
		private SingletonHolder() {
		}
	}
}
